package com.googlecode.geoteacher.logic;

import java.util.ArrayList;
import java.util.List;

import android.graphics.Color;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.maps.android.PolyUtil;
import com.google.maps.android.SphericalUtil;

public abstract class Place {
	protected String name;
	protected boolean display = false;
	protected boolean challenge = false;

	protected Place(String name) {
		if (name == null)
			throw new NullPointerException("name is null");

		this.name = name;
	}

	public String getName() {
		return name;
	}

	public abstract Object displayPermanent(GoogleMap map);

	public abstract Object displaySolution(GoogleMap map);

	public abstract void removeSolution(Object o);

	public abstract LatLng closestPointTo(LatLng position);

	public boolean isDisplay() {
		return display;
	}

	public void setDisplay(boolean display) {
		this.display = display;
	}

	public boolean isChallenge() {
		return challenge;
	}

	public void setChallenge(boolean challenge) {
		this.challenge = challenge;
	}

	public static class Point extends Place {
		private LatLng latLng;

		public Point(String name, LatLng latLng) {
			super(name);

			if (latLng == null)
				throw new NullPointerException("latLng is null");

			this.latLng = latLng;
		}

		public LatLng getLatLng() {
			return latLng;
		}

		@Override
		public String toString() {
			return "Point [name=" + name + "]";
		}

		@Override
		public Object displayPermanent(GoogleMap map) {
			return display(map, false);
		}

		@Override
		public Object displaySolution(GoogleMap map) {
			return display(map, true);
		}

		private Object display(GoogleMap map, boolean solution) {
			Marker marker = map
					.addMarker(new MarkerOptions()
							.position(latLng)
							.title(name)
							.icon(BitmapDescriptorFactory
									.defaultMarker(solution ? BitmapDescriptorFactory.HUE_GREEN
											: BitmapDescriptorFactory.HUE_BLUE)));
			marker.showInfoWindow();
			return marker;
		}

		@Override
		public void removeSolution(Object o) {
			if (o == null || !(o instanceof Marker)) {
				throw new IllegalArgumentException("Not a marker: " + o);
			}

			Marker marker = (Marker) o;
			marker.remove();
		}

		@Override
		public LatLng closestPointTo(LatLng position) {
			return latLng;
		}
	}

	public static class Area extends Place {
		private List<List<LatLng>> border;

		public Area(String name, List<List<LatLng>> border) {
			super(name);

			if (border == null)
				throw new NullPointerException("border is null");

			this.border = border;
		}

		public List<List<LatLng>> getBorder() {
			return border;
		}

		@Override
		public String toString() {
			return "Area [name=" + name + "]";
		}

		@Override
		public Object displayPermanent(GoogleMap map) {
			return display(map, false);
		}

		@Override
		public Object displaySolution(GoogleMap map) {
			return display(map, true);
		}

		private Object display(GoogleMap map, boolean solution) {
			List<Polygon> polygons = new ArrayList<Polygon>();

			for (List<LatLng> line : border) {
				PolygonOptions options = new PolygonOptions().addAll(line);
				if (solution) {
					options.fillColor(Color.argb(100, 0, 255, 0));
				}
				polygons.add(map.addPolygon(options));
			}

			return polygons;
		}

		@SuppressWarnings("unchecked")
		@Override
		public void removeSolution(Object o) {
			if (o == null || !(o instanceof List)) {
				throw new IllegalArgumentException("Not a list: " + o);
			}

			List<Polygon> polygons = (List<Polygon>) o;
			for (Polygon p : polygons) {
				p.remove();
			}
		}

		@Override
		public LatLng closestPointTo(LatLng position) {
			for (List<LatLng> polygon : border) {
				if (PolyUtil.containsLocation(position, polygon, true)) {
					return null;
				}
			}

			double minDistance = Double.MAX_VALUE;
			LatLng closestPoint = null;

			for (List<LatLng> polygon : border) {
				for (LatLng latLng : polygon) {

					double distance = SphericalUtil.computeDistanceBetween(
							latLng, position);

					if (distance < minDistance) {
						minDistance = distance;
						closestPoint = latLng;
					}
				}
			}

			return closestPoint;
		}
	}

	public static class Line extends Place {
		private List<LatLng> line;

		protected Line(String name, List<LatLng> line) {
			super(name);

			if (line == null)
				throw new NullPointerException("line is null");

			this.line = line;
		}

		public List<LatLng> getLine() {
			return line;
		}

		@Override
		public String toString() {
			return "Line [name=" + name + "]";
		}

		@Override
		public Object displayPermanent(GoogleMap map) {
			return display(map, false);
		}

		@Override
		public Object displaySolution(GoogleMap map) {
			return display(map, true);
		}

		private Object display(GoogleMap map, boolean solution) {
			PolylineOptions options = new PolylineOptions().addAll(line);
			if (solution) {
				options.color(Color.GREEN);
			}
			return map.addPolyline(options);
		}

		@Override
		public void removeSolution(Object o) {
			if (o == null || !(o instanceof Polyline)) {
				throw new IllegalArgumentException("Not a polyline: " + o);
			}

			Polyline polyline = (Polyline) o;
			polyline.remove();
		}

		@Override
		public LatLng closestPointTo(LatLng position) {
			double minDistance = Double.MAX_VALUE;
			LatLng closestPoint = null;

			for (LatLng latLng : line) {
				double distance = SphericalUtil.computeDistanceBetween(latLng,
						position);

				if (distance < minDistance) {
					minDistance = distance;
					closestPoint = latLng;
				}
			}

			return closestPoint;
		}
	}
}